JavaScript 有自己的記憶體回收機制,會透過一個稱作 垃圾回收器(garbage collector)
的系統,追蹤和釋放一些不再使用的記憶體空間。然而這並不代表我們可以完全不管記憶體的使用狀況,在程式碼撰寫不當的情況下仍可能導致 記憶體洩漏(Memory Leak)
的情形發生。今天就來談談 JavaScript 的記憶體管理機制吧。
這邊直接引用 MDN 的描述:
不論是哪種程式語言,記憶體生命週期(運作方式)幾乎總是一樣:
- 配置你程式需要的記憶體空間
- 使用配置到的記憶體空間(讀,寫)
- 當不再使用時釋放已被配置的記憶體空間
在所有語言中,第二點的(運作方式)是確定的。第一點以及最後一點在低階語言中是確定的,但是在高階語言如 JavaScript 則通常是不明確的。
JavaScript 的記憶體管理是基於 Stack(堆疊)
和 Heap(堆積)
的概念。Stack
通常用於儲存基本變數和函式調用,而Heap
通常用於儲存複雜資料結構(如物件和函式)。
Stack(堆疊)
:這是一種用於儲存靜態資料的資料結構。靜態資料是指在程式編譯時引擎已知其大小的資料,例如 JavaScript 中的 string
、 number
、 boolean
、 null
和 undefined
。還包括指向物件和函數的引用。靜態資料使用了固定的記憶體空間。
Heap(堆積)
:用於儲存 JavaScript 中的物件
和函式
等。系統不會分配固定的記憶體空間,而是根據需要動態分配空間大小。
這裡借用 geeksforgeeks 的範例圖來幫助理解:
JavaScript 會透過 垃圾回收器(garbage collector)
來檢查和釋放不再使用的記憶體空間,一樣來看 MDN 對其的描述:
高階的語言 (e.g. JavaScript) 有一個叫作垃圾回收器(garbage collector) 的系統,他的工作是追蹤記憶體分配的使用情況,以便自動釋放一些不再使用的記憶體空間。但這個垃圾回收器只是「儘量」做到自動釋放記憶體空間,因為判斷記憶體空間是否要繼續使用,這件事是「不可判定(undecidable)」的(不能用演算法來解決)。
這裡就不詳述關於回收機制的演算法了。
簡單來說,JavaScript 大部分情況下是能自動釋放記憶體的,但它也沒有厲害到能百分百找到「不再被使用的記憶體空間」,可能導致這是我們在開發時要特別注意的地方。
記憶體洩漏(Memory Leak)
是指在程式執行期間,已經不再需要的記憶體區域(例如變數、物件、陣列等)未被正確釋放或回收,導致這些記憶體區域一直占用系統資源,最終可能導致程式性能下降,甚至因記憶體不足導致系統崩潰。
全域變數在整個程式的生命週期中都存在,只有當程式運行結束時才會被釋放。這意味著如果你不再需要某個全域變數,但忘記解除對它的引用,它將一直存在於記憶體中,這可能導致記憶體洩漏。
let x = [1, 2, 3]; // x 是全域變數,故即使之後不再使用,它仍占用記憶體空間
console.log(x);
if (true) {
let y = [4, 5, 6]; // y 是區域變數,故回收機制會在適當時機釋放記憶體空間
console.log(y);
}
閉包(closure)也可能導致記憶體洩漏。閉包會在之後的章節談到,所以這裡可以先看看就好。
這邊先簡單知道,閉包使內部函式能夠訪問外部函式的作用域。函式作用域的變數在函式執行完畢後會被清理,但閉包會在其執行後保持對外部作用域變數的引用,使得這些外部作用域變數即使不再使用卻不會被清除,進而導致記憶體洩漏的問題。
以下是一個例子,其中 largeArray 雖然不是全域變數,
但由於閉包的特性,導致函式執行完畢後 largeArray 未被清除。
function outer() {
let largeArray = [];
return function (num) {
largeArray.push(num);
}
}
const fn = outer();
for (let i = 0; i < 10000000; i++) {
fn(i);
}
今天介紹了 JavaScript 的記憶體管理,談到了 Stack(堆疊)
和 Heap(堆積)
,並簡單說明 垃圾回收器(garbage collector)
的回收機制,以及最重要的是要避免 記憶體洩漏(Memory Leak)
的發生。
參考資料: